/**
 * Copyright (c) 2015-2017, Chill Zhuang 庄骞 (smallchill@163.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jaux.tank.shiro;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.util.AntPathMatcher;

import com.alibaba.druid.util.StringUtils;

import jaux.tank.admin.config.properties.TankProperties;
import jaux.tank.bean.constant.Const;
import jaux.tank.bean.core.ShiroUser;
import jaux.tank.bean.vo.SpringContextHolder;
import jaux.tank.dao.cache.TokenCache;
import jaux.tank.service.system.impl.ConstantFactory;
import jaux.tank.utils.HttpKit;
import jaux.tank.utils.ToolUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * shiro工具类
 *
 * @author dafei, Chill Zhuang
 */
@Slf4j
public class ShiroKit {

    private static final String AUTHORIZATION = "Authorization";

	private static final String NAMES_DELIMETER = ",";
    
    private static final String BEARER_PREFIX = "Bearer ";

    /**
     * 加盐参数
     */
    public static final String HASH_ALGORITHM_NAME = "MD5";

    /**
     * 循环次数
     */
    public static final int HASH_ITERATIONS = 1024;
    
    private ShiroKit() {}
    

    /**
     * shiro密码加密工具类
     *
     * @param credentials 密码
     * @param saltSource 密码盐
     * @return
     */
    public static String md5(String credentials, String saltSource) {
        ByteSource salt = new Md5Hash(saltSource);
        return new SimpleHash(HASH_ALGORITHM_NAME, credentials, salt, HASH_ITERATIONS).toString();
    }

    /**
     * 获取随机盐值
     * @param length
     * @return
     */
    public static String getRandomSalt(int length) {
        return ToolUtil.getRandomString(length);
    }

    /**
     * 获取当前 Subject
     *
     * @return Subject
     */
    public static Subject getSubject() {
        return SecurityUtils.getSubject();
    }

    /**
     * 获取封装的 ShiroUser
     *
     * @return ShiroUser
     */
    public static Optional<ShiroUser> getUser() {
        if (isGuest()) {
            return Optional.empty();
        } 
        ShiroUser user = (ShiroUser) getSubject().getPrincipals().getPrimaryPrincipal();
        if(user ==null) {
        	return getApiRequestUser();
        }
		return Optional.ofNullable(user);
    }
    
    /**
     * 获取api请求的用户.
     *
     * @return the api request user
     */
    public static Optional<ShiroUser> getApiRequestUser() {
    	String token = getRequestToken();
    	if(StringUtils.isEmpty(token))
    		return getUser();
    	TokenCache tc  = SpringContextHolder.getBean(TokenCache.class);
    	if(tc==null)
    		return Optional.empty();
    	return Optional.ofNullable(tc.getUser(token));
    }

	/**
	 * 获取请求token
	 * @return
	 */
    public static String getRequestToken() {
    	return getRequestToken(HttpKit.getRequest());
    }
    
    private static String getRequestToken(HttpServletRequest request) {
		boolean isUrlWithAuthAllowed = false;
        String requestUrl = request.getRequestURI();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        TankProperties tankProps = SpringContextHolder.getBean(TankProperties.class);
        for(String pattern:tankProps.getUrlWithAuth()) {
        	if(pathMatcher.match(pattern, requestUrl) ) {
        		isUrlWithAuthAllowed = true;
        		break;
        	}
        	
        }
        String token = request.getHeader(AUTHORIZATION);
        log.debug("token:{}",token);
        if (token == null && (isUrlWithAuthAllowed)) {
    		token = request.getParameter(AUTHORIZATION);
        }
        if(token !=null && token.startsWith(BEARER_PREFIX)) {
	        token = token.substring(BEARER_PREFIX.length());
        }
		return token;
	}
    /**
     * 从shiro获取session
     *
     */
    public static Session getSession() {
        return getSubject().getSession();
    }

    /**
     * 获取shiro指定的sessionKey
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getSessionAttr(String key) {
        Session session = getSession();
        return session != null ? (T) session.getAttribute(key) : null;
    }

    /**
     * 设置shiro指定的sessionKey
     *
     */
    public static void setSessionAttr(String key, Object value) {
        Session session = getSession();
        session.setAttribute(key, value);
    }

    /**
     * 移除shiro指定的sessionKey
     */
    public static void removeSessionAttr(String key) {
        Session session = getSession();
        if (session != null)
            session.removeAttribute(key);
    }

    /**
     * 验证当前用户是否属于该角色？,使用时与lacksRole 搭配使用
     *
     * @param roleName
     *            角色名
     * @return 属于该角色：true，否则false
     */
    public static boolean hasRole(String roleName) {
        return getSubject() != null && roleName != null
                && roleName.length() > 0 && getSubject().hasRole(roleName);
    }

    /**
     * 与hasRole标签逻辑相反，当用户不属于该角色时验证通过。
     *
     * @param roleName
     *            角色名
     * @return 不属于该角色：true，否则false
     */
    public static boolean lacksRole(String roleName) {
        return !hasRole(roleName);
    }

    /**
     * 验证当前用户是否属于以下任意一个角色。
     *
     * @param roleNames
     *            角色列表
     * @return 属于:true,否则false
     */
    public static boolean hasAnyRoles(String roleNames) {
        boolean hasAnyRole = false;
        Subject subject = getSubject();
        if (subject != null && roleNames != null && roleNames.length() > 0) {
            for (String role : roleNames.split(NAMES_DELIMETER)) {
                if (subject.hasRole(role.trim())) {
                    hasAnyRole = true;
                    break;
                }
            }
        }
        return hasAnyRole;
    }

    /**
     * 验证当前用户是否属于以下所有角色。
     *
     * @param roleNames
     *            角色列表
     * @return 属于:true,否则false
     */
    public static boolean hasAllRoles(String roleNames) {
        boolean hasAllRole = true;
        Subject subject = getSubject();
        if (subject != null && roleNames != null && roleNames.length() > 0) {
            for (String role : roleNames.split(NAMES_DELIMETER)) {
                if (!subject.hasRole(role.trim())) {
                    hasAllRole = false;
                    break;
                }
            }
        }
        return hasAllRole;
    }

    /**
     * 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
     *
     * @param permission
     *            权限名
     * @return 拥有权限：true，否则false
     */
    public static boolean hasPermission(String permission) {
        return getSubject() != null && permission != null
                && permission.length() > 0
                && getSubject().isPermitted(permission);
    }

    /**
     * 与hasPermission标签逻辑相反，当前用户没有制定权限时，验证通过。
     *
     * @param permission
     *            权限名
     * @return 拥有权限：true，否则false
     */
    public static boolean lacksPermission(String permission) {
        return !hasPermission(permission);
    }

    /**
     * 已认证通过的用户。不包含已记住的用户，这是与user标签的区别所在。与notAuthenticated搭配使用
     *
     * @return 通过身份验证：true，否则false
     */
    public static boolean isAuthenticated() {
        return getSubject() != null && getSubject().isAuthenticated();
    }

    /**
     * 未认证通过用户，与authenticated标签相对应。与guest标签的区别是，该标签包含已记住用户。。
     *
     * @return 没有通过身份验证：true，否则false
     */
    public static boolean notAuthenticated() {
        return !isAuthenticated();
    }

    /**
     * 认证通过或已记住的用户。与guset搭配使用。
     *
     * @return 用户：true，否则 false
     */
    public static boolean isUser() {
        return getSubject() != null && getSubject().getPrincipal() != null;
    }

    /**
     * 验证当前用户是否为“访客”，即未认证（包含未记住）的用户。用user搭配使用
     *
     * @return 访客：true，否则false
     */
    public static boolean isGuest() {
        return !isUser();
    }

    /**
     * 输出当前用户信息，通常为登录帐号信息。
     *
     * @return 当前用户信息
     */
    public static String principal() {
        if (getSubject() != null) {
            Object principal = getSubject().getPrincipal();
            return principal.toString();
        }
        return "";
    }

    /**
     * 获取当前用户的部门数据范围的集合
     */
    public static List<String> getDeptDataScope() {
    	Optional<ShiroUser> user = getUser();
    	if(!user.isPresent())
    		return Collections.emptyList();
		String deptId = user.get().getDeptId();
        List<String> subDeptIds = ConstantFactory.me().getSubDeptId(deptId);
        subDeptIds.add(deptId);
        return subDeptIds;
    }

    /**
     * 判断当前用户是否是超级管理员
     */
    public static boolean isAdmin() {
        Optional<ShiroUser> user = ShiroKit.getUser();
        if(!user.isPresent()) {
        	return false;
        }
		List<String> roleList = user.get().getRoleList();
        for (String roleId : roleList) {
            String singleRoleTip = ConstantFactory.me().getSingleRoleTip(roleId);
            if (singleRoleTip.equals(Const.ADMIN_NAME)) {
                return true;
            }
        }
        return false;
    }

}
